const express = require('express');
const WebSocket = require('ws');
const ssh2 = require('ssh2');
const uuidv4 = require('uuid/v4');

const { bootupVM, shutdownVM, recoverVMFromSnapshot } = require('../../zstack.js');

const VM_SESSIONS = {};

// 启动定时任务，检查vm是否超时
const VM_TIMEOUT = 1000 * 60 * 60 * 24 * 3;  // 每个用户最长可以使用3天
const VM_TOUCH_TIMEOUT = 1000 * 60 * 60 * 3; // 3小时不操作，自动释放
const VM_TIMEOUT_CHECK_INTERVAL = 1000 * 60; // 每1分钟检查一次过期状态

function zstack_route(app) {
    const router = express.Router();

    const releaseVM = async (vmid, msg) => {
        app.logger.debug(`释放云资源：${vmid}。\n${msg || ''}\n`);
        if (VM_SESSIONS[vmid] && VM_SESSIONS[vmid].wss) {
            for (let i = 0; i < VM_SESSIONS[vmid].wss.length; i += 1) {
                const wss = VM_SESSIONS[vmid].wss[i];

                for (const ws of wss.clients) {
                    if (ws.readyState === WebSocket.OPEN) {
                        ws.send('release');
                        ws.close();    
                    }
                }

                wss.close();
            }

            if (VM_SESSIONS[vmid].ssh2) {
                VM_SESSIONS[vmid].ssh2.end();
            }
        }

        await app.models.vm.updateOne({
            id: vmid,
            Deleted: false,
        }, {
            UsedBy: null,
            ExpiredAt: null,
        });
        
        delete VM_SESSIONS[vmid];
    }

    setInterval(async () => {
        for (const key in VM_SESSIONS) {
            VM_SESSIONS[key].lastTouch = VM_SESSIONS[key].lastTouch || Date.now();
            VM_SESSIONS[key].createdAt = VM_SESSIONS[key].createdAt || Date.now();

            if ((Date.now() - VM_SESSIONS[key].lastTouch) > VM_TOUCH_TIMEOUT) {
                // 连续x小时未操作，释放vm
                await releaseVM(key, '长时间未操作，自动释放！');
            } else if ((Date.now() - VM_SESSIONS[key].createdAt) > VM_TIMEOUT) {
                // 超时释放vm
                await releaseVM(key, '超时释放！');
            }
        }

        // 没有连接但过期的vm，释放
        const expiredVms = await app.models.vm.find({
            ExpiredAt: { $lt: new Date() },
        }).lean();

        for (let i = 0; i < expiredVms.length; i += 1) {
            const vm = expiredVms[i];

            await releaseVM(vm.id, '过期释放！');
        }
    }, VM_TIMEOUT_CHECK_INTERVAL);

    router.get('/', 
        async (req, res, next) => {
            // 获取当前可用的vm列表
            res.locals.filter = {
                ...res.locals.filter,
                $or: [
                    { UsedBy: null },
                    { UsedBy: req.user.id },

                    // 已过期的vm
                    { ExpiredAt: { $lt: new Date() } },
                ],
                Deleted: false,
                Status: '1',

                // private: {
                //     $or: [
                //         {
                //             $exists: false,
                //         },
                //         {
                //             $eq: req.user.id,
                //         },
                //     ],
                // },
            };

            res.locals.fields = [
                'id',
                'BaseImage',
                'GPU',
                'OS',
                'framework',
                '名称',
                '描述',

                'UsedBy',

                'private',
            ];

            return next();
        },
        router.FindAllDocuments('vm', false, (req, res) => {
            const presetVms = res.locals.data.docs.filter((vm) => vm.private === req.user.UserName).map((vm) => ({
                label: vm.名称,
                id: vm.id,
            }));
            const allPublicVms = res.locals.data.docs.filter((vm) => !vm.private);

            const ret = {
                using: false,
                valid: allPublicVms.length > 0,
                opts: {},
                vms: allPublicVms.map((vm) => ({
                    id: vm.id,
                    label: `${vm.BaseImage}-${vm.GPU}-${vm.OS}-${vm.framework}`,
                })),
                presetVms,
            };
            for (let i = 0; i < allPublicVms.length; i += 1) {
                const vm = allPublicVms[i];

                if (vm.UsedBy === req.user.id) {
                    ret.using = vm.id;

                    ret.selected = {
                        base: vm.BaseImage,
                        gpu: vm.GPU,
                        os: vm.OS,
                        framework: vm.framework,
                    };
                }

                ret.opts['base'] = ret.opts['base'] || [];
                if (vm.BaseImage && ret.opts['base'].findIndex((item) => item === vm.BaseImage) === -1) {
                    ret.opts['base'].push(vm.BaseImage);
                }

                ret.opts['gpu'] = ret.opts['gpu'] || [];
                if (vm.GPU && ret.opts['gpu'].findIndex((item) => item === vm.GPU) === -1) {
                    ret.opts['gpu'].push(vm.GPU);
                }

                ret.opts['os'] = ret.opts['os'] || [];
                if (vm.OS && ret.opts['os'].findIndex((item) => item === vm.OS) === -1) {
                    ret.opts['os'].push(vm.OS);
                }

                ret.opts['framework'] = ret.opts['framework'] || [];
                if (vm.framework && ret.opts['framework'].findIndex((item) => item === vm.framework) === -1) {
                    ret.opts['framework'].push(vm.framework);
                }
            }

            res.addData(ret, true);
        }),
    );

    router.post('/connect', 
        async (req, res, next) => {
            // 获取对应vm
            const {
                vm,
            } = req.body;

            // 检查对应vm是否存在
            const theVM = await res.app.models.vm.findOne({
                id: vm,
                Deleted: false,
            });

            if (!theVM) {
                res.app.logger.error(`vm not exists：${vm}`);
                res.makeError(400, '请求的云资源不存在！');
                return next('route');
            }

            // 检查对应vm是否正在被使用
            let userVM;
            if (theVM.UsedBy) {
                if (theVM.UsedBy === req.user.id) {
                    userVM = theVM;
                } else {
                    res.makeError(401, '请求的云资源正在被使用！');
                    return next('route');
                }
            }

            // 检查当前用户是否正在使用某个vm
            if ((theVM.private !== req.user.UserName)) {
                userVM = userVM || await res.app.models.vm.findOne({
                    UsedBy: req.user.id,
                    Deleted: false,
                });
            }

            // 恢复快照
            if (!userVM && theVM) {
                if (theVM.uuid && theVM.SnapshotUuid) {
                    const shutdownRes = await shutdownVM(theVM.uuid);
                    if (!shutdownRes) {
                        res.app.logger.error(`云主机关机失败：${theVM.uuid}`);
                        res.makeError(500, '云资源创建失败！');
                        return next('route');
                    }
                    res.app.logger.debug(`云主机关机成功：${theVM.uuid}`);
                    const recoverRes = await recoverVMFromSnapshot(theVM.SnapshotUuid)
                    if (!recoverRes) {
                        res.app.logger.error(`云主机恢复快照失败：${theVM.uuid}`);
                        res.makeError(500, '云资源创建失败！');
                        return next('route');
                    }
                    res.app.logger.debug(`云主机恢复快照成功：${theVM.uuid}`);
                    const bootupRes = await bootupVM(theVM.uuid);
                    if (!bootupRes) {
                        res.app.logger.error(`云主机开机失败：${theVM.uuid}`);
                        res.makeError(500, '云资源创建失败！');
                        return next('route');
                    }
                    res.app.logger.debug(`云主机开机成功：${theVM.uuid}`);
                } else {
                    res.app.logger.debug(`直连云主机：${theVM.IP}`);
                }

                VM_SESSIONS[theVM.id] = VM_SESSIONS[theVM.id] || {};
            }

            const targetVM = userVM || theVM;
            res.app.logger.debug(`连接云资源(IP)：${targetVM.IP}`);

            // 生成uuid，用于标识socket连接
            const uuid = uuidv4();

            // 清理已有的ws
            VM_SESSIONS[targetVM.id] = VM_SESSIONS[targetVM.id] || {};
            VM_SESSIONS[targetVM.id].wss = VM_SESSIONS[targetVM.id].wss || [];

            for (let i = 0; i < VM_SESSIONS[targetVM.id].wss.length; i += 1) {
                const ws = VM_SESSIONS[targetVM.id].wss[i];
                ws.close();
            }

            // 启动socket
            const wss = new WebSocket.Server({
                server: res.app.server,
                // noServer: true,
            });

            VM_SESSIONS[targetVM.id].wss = [wss];

            wss.on('open', () => {
                res.app.logger.debug(`ws open: ${uuid}`);
            });
            wss.on('ready', () => {
                res.app.logger.debug(`ws ready: ${uuid}`);
            });

            wss.on('connection', (ws, request) => {
                res.app.logger.debug(`ws connecting: ${uuid}`);
                res.app.logger.debug(`连接云资源2(IP)：${targetVM.IP}`);
                // 检查连接是否合法
                if (request.url !== `/cloud/vm/connect/${uuid}`) {
                    res.app.logger.error(`connection incorrect：${uuid}, ${request.url}`);
                    ws.send('请求的云资源不存在！');
                    ws.close();
                    wss.close();

                    return;
                }

                // ssh2
                const conn = new ssh2.Client();
                let connTimer;
                conn.on('ready', () => {
                    clearInterval(connTimer);

                    if (VM_SESSIONS[targetVM.id]) {
                        VM_SESSIONS[targetVM.id].ssh2 = conn;
                    }

                    conn.shell((err, stream) => {
                        if (err) {
                            res.app.logger.error(err.message);
                            ws.send('终端连接失败！');
                            return;
                        }

                        ws.send('[[cmd]]: connected');

                        ws.on('message', (data) => {
                            if (data.toString() === 'ping') {
                                ws.send('pong');
                                return;
                            }
                            
                            // 处理调整窗口大小的请求
                            if (data.toString().startsWith('[[resize]]: ')) {
                                const size = data.toString().split(' ')[1].split('x');
                                stream.setWindow(parseInt(size[0]), parseInt(size[1]));
                                stream.emit('window-change', parseInt(size[0]), parseInt(size[1]));
                                
                                return;
                            }
                            
                            stream.write(data);

                            // 记录最后一次操作时间，用于判断是否超时
                            VM_SESSIONS[targetVM.id].lastTouch = Date.now();
                        });

                        stream.on('data', (data) => {
                            ws.send(data.toString());
                        });

                        stream.on('close', (code, signal) => {
                            res.app.logger.debug(`Stream :: close :: code: ${code}, signal: ${signal || ''}`);
                            ws.send('exit');
                            ws.close();
                            wss.close();
                        });
                    });
                });

                conn.on('error', (err) => {
                    res.app.logger.error(err.message);
                    // ws.send('远程连接失败！');
                });

                const startTime = Date.now();
                connTimer = setInterval(() => {
                    conn.connect({
                        host: targetVM.IP,
                        port: 22,
                        username: targetVM.VMUserName,
                        password: targetVM.VMPassword,
                    });

                    if ((Date.now() - startTime) > 1000 * 60 * 5) {
                        clearInterval(connTimer);
                        ws.send('远程连接超时！');
                    }

                    res.app.logger.debug('尝试连接远程主机...');

                }, userVM ? 3000 : 10000);
            });

            wss.on('error', (err) => {
                res.app.logger.error(err.message);
            });

            // 返回socket连接地址
            res.app.logger.debug(`Sending new UUID: ${uuid}`);
            res.addData({
                uuid,
            }, true);

            // 第一次使用，更新vm的使用状态
            if (!userVM && theVM) {
                await res.app.models.vm.updateOne({
                    id: targetVM.id,
                    Deleted: false,
                }, {
                    UsedBy: req.user.id,
                    ExpiredAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 3),
                });

                VM_SESSIONS[targetVM.id].createdAt = Date.now();
                VM_SESSIONS[targetVM.id].lastTouch = Date.now();
            }

            return next();
        },
    );

    router.post('/release', async (req, res, next) => {
        // 获取对应vm
        const {
            vm,
        } = req.body;

        // 检查对应vm是否存在
        const theVM = await res.app.models.vm.findOne({
            id: vm,
            UsedBy: req.user.id,
            Deleted: false,
        });

        if (!theVM) {
            res.app.logger.error(`vm not exists：${vm}`);
            res.makeError(400, '请求的云资源不存在！');
            return next('route');
        }

        await releaseVM(theVM.id);
        
        return next();
    });

    return router;
}

Object.defineProperty(zstack_route, 'isRouterFunc', {
    value: true
})

module.exports = zstack_route;
